假設今天我有兩個Line Setting 都是相同環境做使用,且結構相同appsettings.json
{
"DogLineSetting": {
"ChannelId": "dog_channel_id",
"ChannelSecret": "dog_channel_secret",
"Info": {
"Name": "dog",
"PictureUrl": "dog_picture_url"
}
},
"CatLineSetting": {
"ChannelId": "cat_channel_id",
"ChannelSecret": "cat_channel_secret",
"Info": {
"Name": "cat",
"PictureUrl": "cat_picture_url"
}
}
}
可以在設定Option的時候使用命名的方式為綁定的Option 命名
這種方式在dotnet core 其實很常見,諸如:HttpClient,Cors 等等。
調整一下ServiceCollection.Configure
方法
舊(不使用命名)
var serviceProvider = new ServiceCollection()
.AddOptions()
.Configure<LineSetting>(configuration.GetSection("Line"))
使用命名,用 Configure\<T>(name:string, config:IConfiguration)
的多載
var serviceProvider = new ServiceCollection()
.AddOptions()
.Configure<LineSetting>("Dog", configuration.GetSection("DogLineSetting"))
.Configure<LineSetting>("Cat", cnfiguration.GetSection("CatLineSetting"))
注入,注意這邊使用的是IOptionsSnapshot
,後面會提到為什麼不是IOptions
public class LineService
{
private readonly LineSetting _lineSetting;
public LineService(IOptionsSnapshot<LineSetting> lineSetting)
{
_lineSetting = lineSetting.Get("Dog");
}
public void PrintLineAppName()
{
Console.WriteLine(_lineSetting.Info.Name);
}
}
我們偷偷的脫去AddOptions()
的衣裳
OptionsServiceCollectionExtensions.cs
public static IServiceCollection AddOptions(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(UnnamedOptionsManager<>)));
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
return services;
}
可以看見IOptions<>
的實作為UnnamedOptionsManager<>
而IOptionsSnapshot<>
的實作為OptionsManager<>
我們先來看看UnnamedOptionsManager<>
的實作
UnnamedOptionsManager.cs
public TOptions Value
{
get
{
if (_value is TOptions value)
{
return value;
}
lock (_syncObj ?? Interlocked.CompareExchange(ref _syncObj, new object(), null) ?? _syncObj)
{
return _value ??= _factory.Create(Options.DefaultName);
}
}
}
可以注意到當value 沒有值的時候,會從IOptionFactory
中取得一個 name 為"default"的 TOption的Instance
因此他是 unnamed
的 Option
OptionsManager.cs
public TOptions Value => Get(Options.DefaultName);
public virtual TOptions Get(string name)
{
name = name ?? Options.DefaultName;
if (!_cache.TryGetValue(name, out TOptions options))
{
// Store the options in our instance cache. Avoid closure on fast path by storing state into scoped locals.
IOptionsFactory<TOptions> localFactory = _factory;
string localName = name;
options = _cache.GetOrAdd(name, () => localFactory.Create(localName));
}
return options;
}
當get Value
的屬性時,取得的是一個unname
的Option。
而Get方法則會從當前快取(Scoped
)中找值,如果沒有的話就靠factory 產一個
可以注意到他的參數是有name
,意味著可以建立具名的Options